// service/log_service/action_log.go
// =============================================================================
// | 包: log_service                                                         |
// | 功能: 提供日志服务相关功能，包括操作日志记录、查询和管理                 |
// | 版本: 1.0.0                                                             |
// =============================================================================
package log_service

import (
	"blog_server/core"        // 核心功能包 - 提供核心功能支持
	"blog_server/global"      // 全局变量包 - 提供全局访问点
	"blog_server/models"      // 数据模型包 - 提供数据模型定义
	"blog_server/models/enum" // 枚举包 - 提供枚举类型定义
	"blog_server/utils/jwts"  // JWT工具包 - 提供令牌解析功能
	"bytes"                   // 字节包 - 提供字节操作功能
	"encoding/json"          // JSON包 - 提供JSON编码解码
	"fmt"                    // 格式化包 - 提供字符串格式化
	"io"                     // IO包 - 提供IO操作功能
	"net/http"               // HTTP包 - 提供HTTP相关功能
	"reflect"                // 反射包 - 提供反射功能
	"strings"                // 字符串包 - 提供字符串操作

	"github.com/gin-gonic/gin"   // Web框架 - 提供HTTP路由和处理功能
	"github.com/pkg/errors"      // 第三方错误处理包 - 提供增强的错误处理
	"github.com/sirupsen/logrus" // 日志库 - 提供结构化日志记录功能
)

// =============================================================================
// | 结构体: ActionLog                                                       |
// | 功能: 操作日志结构体                                                    |
// | 说明: 封装操作日志相关的属性和方法，用于记录用户操作信息                 |
// |----------------------------------------------------------------------------|
// | 字段: c                  - *gin.Context      - Gin上下文对象            |
// | 字段: level              - enum.LogLevelType - 日志级别                 |
// | 字段: title              - string            - 日志标题                  |
// | 字段: requestBody        - []byte            - 请求体内容                 |
// | 字段: responseBody       - []byte            - 响应体内容                |
// | 字段: log                - *models.LogModel  - 日志数据模型指针          |
// | 字段: showRequestHeader  - bool              - 是否显示请求头            |
// | 字段: showRequest        - bool              - 是否显示请求信息          |
// | 字段: showResponseHeader - bool              - 是否显示响应头            |
// | 字段: showResponse       - bool              - 是否显示响应信息          |
// | 字段: itemList           - []string          - 日志项列表                |
// | 字段: responseHeader     - http.Header       - 响应头信息                |
// | 字段: isMiddleware       - bool              - 是否为中间件调用          |
// =============================================================================
type ActionLog struct {
	c                  *gin.Context      // Gin上下文对象
	level              enum.LogLevelType // 日志级别
	title              string            // 日志标题
	requestBody        []byte            // 请求体内容
	responseBody       []byte            // 响应体内容
	log                *models.LogModel  // 日志数据模型指针
	showRequestHeader  bool
	showRequest        bool // 控制是否显示请求信息
	showResponseHeader bool
	showResponse       bool // 控制是否显示响应信息
	itemList           []string
	responseHeader     http.Header
	isMiddleware       bool
}

// =============================================================================
// | 方法: ShowRequest                                                       |
// | 接收者: @ac - *ActionLog (操作日志实例)                                  |
// | 功能: 设置显示请求信息                                                  |
// =============================================================================
func (ac *ActionLog) ShowRequest() {
	ac.showRequest = true // 将showRequest字段设置为true
}

// =============================================================================
// | 方法: ShowResponse                                                      |
// | 接收者: @ac - *ActionLog (操作日志实例)                                  |
// | 功能: 设置显示响应信息                                                  |
// =============================================================================
func (ac *ActionLog) ShowResponse() {
	ac.showResponse = true // 将showResponse字段设置为true
}

// =============================================================================
// | 方法: SetTitle                                                          |
// | 接收者: @ac - *ActionLog (操作日志实例)                                  |
// | 参数: @title - string (日志标题)                                         |
// | 功能: 设置操作日志的标题                                                |
// =============================================================================
func (ac *ActionLog) SetTitle(title string) {
	ac.title = title // 设置日志标题
}

// =============================================================================
// | 方法: SetLevel                                                          |
// | 接收者: @ac - *ActionLog (操作日志实例)                                  |
// | 参数: @level - enum.LogLevelType (日志级别)                              |
// | 功能: 设置操作日志的级别                                                |
// =============================================================================
func (ac *ActionLog) SetLevel(level enum.LogLevelType) {
	ac.level = level // 设置日志级别
}

// =============================================================================
// | 方法: SetLink                                                           |
// | 接收者: @ac - *ActionLog (操作日志实例)                                  |
// | 参数: @label - string (链接标签)                                         |
// |       @href - string (链接地址)                                          |
// | 功能: 添加链接类型的日志项                                              |
// =============================================================================
func (ac *ActionLog) SetLink(label string, href string) {
	ac.itemList = append(ac.itemList, fmt.Sprintf(
		`<div class="log_item link">
            <div class="log_item_label">%s</div>
            <a href="%s" target="_blank">%s</a>
        </div>`,
		label, href, href,
	))
}

// =============================================================================
// | 方法: SetImage                                                          |
// | 接收者: @ac - *ActionLog (操作日志实例)                                  |
// | 参数: @src - string (图片源地址)                                         |
// | 功能: 添加图片类型的日志项                                              |
// =============================================================================
func (ac *ActionLog) SetImage(src string) {
	ac.itemList = append(ac.itemList, fmt.Sprintf("%s|", src, "%s|", src))
}

// =============================================================================
// | 方法: ShowRequestHeader                                                 |
// | 接收者: @ac - *ActionLog (操作日志实例)                                  |
// | 功能: 启用请求头显示功能                                                |
// =============================================================================
func (ac *ActionLog) ShowRequestHeader() {
	ac.showRequestHeader = true
}

// =============================================================================
// | 方法: ShowResponseHeader                                                |
// | 接收者: @ac - *ActionLog (操作日志实例)                                  |
// | 功能: 启用响应头显示功能                                                |
// =============================================================================
func (ac *ActionLog) ShowResponseHeader() {
	ac.showResponseHeader = true
}

// =============================================================================
// | 方法: setItem                                                           |
// | 接收者: @ac - *ActionLog (操作日志实例)                                  |
// | 参数: @label - string (日志项标签)                                       |
// |       @value - any (日志项值)                                            |
// |       @logLevelType - enum.LogLevelType (日志级别)                       |
// | 功能: 添加日志项到列表中，内部方法                                      |
// =============================================================================
func (ac *ActionLog) setItem(label string, value any, logLevelType enum.LogLevelType) {

	var v string
	t := reflect.TypeOf(value) // 使用反射获取值的实际类型

	// switch 根据值的具体类型进行不同的转换处理
	switch t.Kind() {
	case reflect.Struct, reflect.Map, reflect.Slice: // 对于复杂类型（结构体、映射、切片），使用JSON序列化
		byteData, _ := json.Marshal(value) // 将复杂类型转换为JSON字节数据
		v = string(byteData)               // 将JSON字节数据转换为字符串
	default:
		v = fmt.Sprintf("%v", value) // 使用fmt.Sprintf将值格式化为字符串
	}

	ac.itemList = append(ac.itemList,
		fmt.Sprintf("<div class=\"%s\"><div class=\"log_item_label\">%s</div>%s<div>",
			logLevelType, label, v))
}

// =============================================================================
// | 方法: SetItem                                                           |
// | 接收者: @ac - *ActionLog (操作日志实例)                                  |
// | 参数: @label - string (日志项标签)                                       |
// |       @value - any (日志项值)                                            |
// | 功能: 添加默认信息级别的日志项                                          |
// =============================================================================
func (ac *ActionLog) SetItem(label string, value any) {
	ac.setItem(label, value, enum.LogInfoLevel)
}

// =============================================================================
// | 方法: SetItemInfo                                                       |
// | 接收者: @ac - *ActionLog (操作日志实例)                                  |
// | 参数: @label - string (日志项标签)                                       |
// |       @value - any (日志项值)                                            |
// | 功能: 添加信息级别的日志项                                              |
// =============================================================================
func (ac *ActionLog) SetItemInfo(label string, value any) {
	ac.setItem(label, value, enum.LogInfoLevel)
}

// =============================================================================
// | 方法: SetItemWarn                                                       |
// | 接收者: @ac - *ActionLog (操作日志实例)                                  |
// | 参数: @label - string (日志项标签)                                       |
// |       @value - any (日志项值)                                            |
// | 功能: 添加警告级别的日志项                                              |
// =============================================================================
func (ac *ActionLog) SetItemWarn(label string, value any) {
	ac.setItem(label, value, enum.LogWarnLevel)
}

// =============================================================================
// | 方法: SetItemError                                                      |
// | 接收者: @ac - *ActionLog (操作日志实例)                                  |
// | 参数: @label - string (日志项标签)                                       |
// |       @value - any (日志项值)                                            |
// | 功能: 添加错误级别的日志项                                              |
// =============================================================================
func (ac *ActionLog) SetItemError(label string, value any) {
	ac.setItem(label, value, enum.LogErrLevel)
}

// =============================================================================
// | 方法: SetError                                                          |
// | 接收者: @ac - *ActionLog (操作日志实例)                                  |
// | 参数: @label - string (错误标签)                                         |
// |       @err - error (错误对象)                                            |
// | 功能: 设置错误日志项的方法                                              |
// | 说明: 记录错误信息并添加调用栈                                           |
// =============================================================================
func (ac *ActionLog) SetError(label string, err error) {
	// fmt.Println(label, err)
	msg := errors.WithStack(err) // 为错误添加调用栈信息
	// fmt.Printf("%+v\n", msg)     // 格式化输出错误详情
	logrus.Errorf("%s %s", label, err.Error())

	ac.itemList = append(ac.itemList, fmt.Sprintf(
		`<div class="log">%s: %s %T \n%+v</div>`,
		label, err, err, msg,
	))
}

// =============================================================================
// | 方法: SetRequest                                                        |
// | 接收者: @ac - *ActionLog (操作日志实例)                                  |
// | 参数: @c - *gin.Context (Gin上下文对象)                                 |
// | 功能: 从请求中读取并设置请求体内容                                      |
// | 说明: 读取请求体并保存，同时保持请求体可重用                             |
// =============================================================================
func (ac *ActionLog) SetRequest(c *gin.Context) {
	byteData, err := io.ReadAll(c.Request.Body) // 读取请求体内容
	if err != nil {
		logrus.Errorf(err.Error()) // 读取失败时输出错误日志
	}
	// fmt.Println("body:", string(byteData))
	c.Request.Body = io.NopCloser(bytes.NewReader(byteData)) // 重置请求体，保持可重用
	ac.requestBody = byteData                                // 设置请求体内容
}

// =============================================================================
// | 方法: SetResponse                                                       |
// | 接收者: @ac - *ActionLog (操作日志实例)                                  |
// | 参数: @data - []byte (响应数据)                                          |
// | 功能: 设置响应体内容                                                    |
// =============================================================================
func (ac *ActionLog) SetResponse(data []byte) {
	ac.responseBody = data // 设置响应体内容
}

// =============================================================================
// | 方法: SetResponseHeader                                                 |
// | 接收者: @ac - *ActionLog (操作日志实例)                                  |
// | 参数: @header - http.Header (响应头信息)                                |
// | 功能: 设置响应头信息                                                    |
// =============================================================================
func (ac *ActionLog) SetResponseHeader(header http.Header) {
	ac.responseHeader = header
}

// =============================================================================
// | 方法: MiddlewareSave                                                    |
// | 接收者: @ac - *ActionLog (操作日志实例)                                  |
// | 功能: 中间件调用的日志保存方法                                          |
// | 说明: 处理中间件中的日志保存逻辑，包括响应头和响应体的记录               |
// =============================================================================
func (ac *ActionLog) MiddlewareSave() {
	if ac.log == nil { // 检查日志对象是否为空
		// 创建日志记录
		ac.isMiddleware = true
		ac.Save() // 调用保存方法
		return    // 保存完成后立即返回
	}

	// -------- 在视图里面save过，属于更新 --------
	// 响应头
	if ac.showResponseHeader {
		byteData, _ := json.Marshal(ac.responseHeader)
		ac.itemList = append(ac.itemList, fmt.Sprintf(
			"<div class=\"log_request_header\">\n\t<div class=\"log_request_body\">\n\t\t<pre class=\"log_json_body\">%s</pre>\n\t</div>\n</div>",
			string(byteData)))
	}

	// 设置响应
	if ac.showResponse {
		ac.itemList = append(ac.itemList, fmt.Sprintf("<div class=\"log_response\">\n\t<pre class=\"log_json_body\">%s</pre>\n</div>",
			string(ac.responseBody), // 响应体内容
		))
	}
	ac.Save()

}

// =============================================================================
// | 方法: Save                                                              |
// | 接收者: @ac - ActionLog (操作日志实例)                                   |
// | 返回: @id - uint (日志ID)                                                |
// | 功能: 保存操作日志到数据库                                              |
// | 说明: 根据日志是否已存在，执行更新或创建操作                             |
// |----------------------------------------------------------------------------|
// | 流程:                                                                   |
// | 1. 检查是否需要保存日志                                                 |
// | 2. 若日志已存在，更新日志内容                                           |
// | 3. 若日志不存在，创建新的日志记录                                       |
// | 4. 保存日志到数据库并返回日志ID                                         |
// =============================================================================
func (ac ActionLog) Save() (id uint) {
	// 获取saveLog标志值 - 验证是否保存日志
	_saveLog, _ := ac.c.Get("saveLog")

	// 将接口类型断言为bool类型
	saveLog, _ := _saveLog.(bool)

	// 检查是否需要保存日志，如果saveLog为false则直接返回
	if !saveLog {
		return
	}

	// 方案1：save只能在日志的响应中间件里面调用 x
	// 方案2：在视图里面调Save，需要返回日志的id √

	if ac.log != nil {
		// 将新的日志项列表用换行符连接成字符串
		newContent := strings.Join(ac.itemList, "\n")
		// 将新内容追加到已有日志内容后面
		content := ac.log.Content + "\n" + newContent

		// 之前已经save过了，那就是更新
		global.DB.Model(ac.log).Updates(map[string]any{
			"content": content, // 更新数据库中的content字段
		})

		// 清空临时日志项列表，准备下一次记录
		ac.itemList = []string{}
		return
	}

	var newItemList []string

	// 请求头
	if ac.showRequestHeader {
		byteData, _ := json.Marshal(ac.c.Request.Header)
		newItemList = append(newItemList, fmt.Sprintf("<div class=\"log_request_header\">\n\t<div class=\"log_request_body\">\n\t\t<pre class=\"log_json_body\">%s</pre>\n\t</div>\n</div>", string(byteData)))
	}

	// 设置请求
	if ac.showRequest {
		newItemList = append(newItemList, fmt.Sprintf("<div class=\"log_request\">\n\t<div class=\"log_request_head\">\n\t\t%s<span class=\"l\">%s %s</span>\n\t</div>\n\t<pre class=\"log_json_body\">%s</pre>\n</div>",
			strings.ToLower(ac.c.Request.Method),
			ac.c.Request.Method,       // HTTP方法 (GET/POST等)
			ac.c.Request.URL.String(), // 请求URL
			string(ac.requestBody),    // 请求体内容
		))
	}
	// 中间的一些content
	newItemList = append(newItemList, ac.itemList...)

	if ac.isMiddleware {
		// 响应头
		if ac.showResponseHeader {
			byteData, _ := json.Marshal(ac.responseHeader)
			newItemList = append(newItemList, fmt.Sprintf(
				"<div class=\"log_request_header\">\n\t<div class=\"log_request_body\">\n\t\t<pre class=\"log_json_body\">%s</pre>\n\t</div>\n</div>",
				string(byteData)))
		}

		// 设置响应
		if ac.showResponse {
			newItemList = append(newItemList, fmt.Sprintf("<div class=\"log_response\">\n\t<pre class=\"log_json_body\">%s</pre>\n</div>",
				string(ac.responseBody), // 响应体内容
			))
		}

	}

	ip := ac.c.ClientIP()      // 获取客户端IP地址
	addr := core.GetIpAddr(ip) // 获取IP对应的地理位置

	// ------ token 面获取 用户id ------
	claims, err := jwts.ParseTokenByGin(ac.c)
	userID := uint(0)
	if err == nil && claims != nil {
		userID = claims.UserID
	}
	// ------ token 面获取 用户id ------

	log := models.LogModel{ // 创建新的日志模型实例
		LogType: enum.ActionLogType, // 日志类型为操作日志
		Title:   ac.title,           // 日志标题
		Content: strings.Join(newItemList, "\n"),
		Level:   ac.level, // 日志级别
		UserID:  userID,   // 用户ID
		IP:      ip,       // IP地址
		Addr:    addr,     // 地理位置
	}

	err = global.DB.Create(&log).Error // 保存日志到数据库

	if err != nil {
		logrus.Errorf("日志创建失败 %s", err) // 保存失败时输出错误日志
		return
	}
	ac.log = &log // 保存成功后，更新日志指针，用于后续可能的更新操作
	ac.itemList = []string{} // 清空日志项列表，准备下一次记录
	return ac.log.ID
}

// =============================================================================
// | 函数: NewActionLogByGin                                                 |
// | 参数: @c - *gin.Context (Gin上下文对象)                                 |
// | 返回: @*ActionLog - 操作日志对象指针                                    |
// | 功能: 根据Gin上下文创建新的操作日志对象                                 |
// =============================================================================
func NewActionLogByGin(c *gin.Context) *ActionLog {
	return &ActionLog{ // 创建并返回新的操作日志对象
		c: c,
	}
}

// =============================================================================
// | 函数: GetLog                                                            |
// | 参数: @c - *gin.Context (Gin上下文对象)                                 |
// | 返回: @*ActionLog - 操作日志对象指针                                    |
// | 功能: 从上下文获取操作日志对象                                          |
// | 说明: 尝试从上下文获取日志对象，若不存在则创建新的日志对象              |
// =============================================================================
func GetLog(c *gin.Context) *ActionLog {
	_log, ok := c.Get("log") // 尝试从上下文中获取日志对象
	if !ok {
		return NewActionLogByGin(c) // 上下文没有日志对象时，创建新的日志对象
	}
	log, _ok := _log.(*ActionLog) // 类型断言
	if !_ok {
		return NewActionLogByGin(c) // 类型断言失败时，创建新的日志对象
	}

	c.Set("saveLog", true)
	return log // 返回从上下文获取的日志对象
}
